
#
# ValuesPanel.py - A generic wx.Panel subclass that creates a static box
# with text labels and entry fields
# 
# Written by John Edwardson

import wx

class ValuePanel(wx.Panel):
    def __init__(self, parent, fields, defaults, title=None, flag=wx.HORIZONTAL,  *args, **kwds):
        '''Creates a new ValuesPanel object. 
        fields = an array of (key, display) tuples  
             key = A dictionary key that will be used to reference items in the panel.  We refer
                    to these keys as the panel namespace
             fields[1] = display text
        defaults = a dictionary of default values with keys from the panel namespace   
        title = title for static box, None for no static box
        flag = orientation of the panel'''
        wx.Panel.__init__(self, parent, *args, **kwds)

        self.boxdict = {}
        self.textdict = {}        
        
        self.makeText(fields)
        self.makeBox(fields)
        self.sizer = self.makeSizer(title, flag, fields)
        self.setProps(defaults)        
        self.SetSizer(self.sizer)
        self.sizer.Fit(self)
    
    def makeSizer(self, boxtitle, flag, labels):
        '''Makes sizer and adds widgets'''
        sizer = None
        if boxtitle:
            static = wx.StaticBox(self, wx.ID_ANY, boxtitle)
            static.Lower()
            sizer = wx.StaticBoxSizer(static, flag)
            
        else:
            sizer = wx.BoxSizer(flag)
        for label in labels:
            key = label[0]
            subsizer = wx.BoxSizer(wx.VERTICAL)
            subsizer.Add(self.textdict[key], 0, wx.ALL | wx.ALIGN_CENTER_HORIZONTAL, 5)
            subsizer.Add(self.boxdict[key], 0, wx.ALL | wx.ALIGN_CENTER_HORIZONTAL, 5)            
            sizer.Add(subsizer, 1, wx.ALL | wx.EXPAND, 5)            
        return sizer

    def makeText(self, labels):
        '''Creates static text labels'''
        for label in labels:
            text = self.textdict[label[0]] = wx.StaticText(self, wx.ID_ANY, label[1])

    def makeBox(self, labels):
        '''Creates textctl fields'''
        for label in labels:
            box = self.boxdict[label[0]] =  wx.TextCtrl(self, wx.ID_ANY, "")    
        
    def setProps(self, props):
        #set defaults        
        if props:
            for k in self.boxdict:
                self.boxdict[k].SetValue(str(props[k]))

 
    def getProps(self, dtypes):
        '''Returns the contents of the fields as a dict.
        dtypes = a dict of desired datatypes using the panel namespace'''
        propdict = {}
        for k in self.boxdict:
            try: #if the datatype is not defined default to string
                propdict[k] = dtypes[k](self.boxdict[k].GetValue())
            except KeyError:
                propdict[k] = self.boxdict[k].GetValue()
        return propdict
    

    def checkInput(self, dtypes, tests=None, optional=[]):
        '''Checks input for errors. 
           dtypes = dictionary of desired datatypes
           tests = an array of tuples (boolstr,msg).  If not boolstr, msg is returned
           The boolstr is evaluated using using the panel namespace'''
        props = {}
        
        for k in self.boxdict:
            if self.boxdict[k].GetValue() == '' and k not in optional:
                return "No " + k + " Entered" 
        try:
            props = self.getProps(dtypes)
        except ValueError:
            return "Invalid Input" 

        if tests:
            for test in tests:
                bule = eval(test[0], props) 
                if bule: return test[1] 
        return ""

class MultiValuePanel(wx.Panel):
    def __init__(self, parent, fields, titles, props, flag = wx.HORIZONTAL, *args, **kwds):
        '''Creates a dialog with multiple ValuePanels
        panels = array of lists (field1, ... , fieldN), where fields are as in ValuePanel
        titles = array of titles for boxes. Set a component to None if you don't wish to show a staticbox around
                the corresponding panel
        props = dictionary of default properties'''
        wx.Panel.__init__(self, parent, *args, **kwds)
        self.panels = []
        self.flag = flag
        self.setProps(props)
        self.setFields(fields, titles)
        self.createPanels() 
        self.setLayout()  
    
    def setFields(self, fields, titles):
        self.fields = fields
        self.titles = titles

    def setProps(self, props):
        self.props = props

    def createPanels(self):
        self.panelsizer = None
        if self.flag == wx.VERTICAL:
            self.panelsizer = wx.BoxSizer(wx.HORIZONTAL)
        else:
            self.panelsizer = wx.BoxSizer(wx.VERTICAL)
        for field,title in zip(self.fields,self.titles):
            additem = ValuePanel(self, field, self.props, title=title, flag = self.flag)
            self.panels.append(additem)
            self.panelsizer.Add(additem, 1, wx.ALL | wx.EXPAND, 5)

    def setLayout(self):
        self.SetSizer(self.panelsizer)
        self.panelsizer.Fit(self)
        self.Layout()

    def getOutput(self, types):
        props = []
        for panel in self.panels:
            props += panel.getProps(types).items()
        return dict(props)

class MultiValueDialog(wx.Dialog):
    def __init__(self, parent, fields=[], titles=[], props=[], flag=wx.HORIZONTAL, *args, **kwds):
        '''Creates a dialog with multiple valuepanels
        panels = array of lists [field1, ... , fieldN], where fields are as in ValuePanel
        titles = array of titles for boxes. Set a component to None if you don't wish to show a staticbox around
                the corresponding panel
        props = dictionary of default properties'''
        wx.Dialog.__init__(self, parent, *args, **kwds)
        self.fields = fields
        self.titles = titles
        self.props = props
        self.flag = flag
        self.types = {}
        self.makePanel()
        self.mpanel = MultiValuePanel(self, self.fields, self.titles, self.props, flag=self.flag)
        self.createButtons()
        self.setLayout()
   
    def makePanel(self):
        '''This is an example.  Override to add custom settings'''
        if not self.fields:
            self.fields = []
            self.fields.append(('name', 'What is your name?'))
            self.fields.append(('quest', 'What is your quest?'))
            self.fields.append(('color', 'What is your favorite color?'))   
        return

    def createButtons(self):
        ok_button = wx.Button(self, wx.ID_OK, "Ok")
        canc_button = wx.Button(self, wx.ID_CANCEL, "Cancel")
        self.button_sizer = wx.BoxSizer(wx.HORIZONTAL)
        self.button_sizer.Add(ok_button, 0, wx.ALL|wx.ALIGN_CENTER_VERTICAL, 5)
        self.button_sizer.Add(canc_button, 0, wx.ALL|wx.ALIGN_CENTER_VERTICAL, 5)        
        self.Bind(wx.EVT_BUTTON, self.OnOk, ok_button)
        self.Bind(wx.EVT_BUTTON, self.OnCancel, canc_button)
    
    def setLayout(self):
        sizer = wx.BoxSizer(wx.VERTICAL)
        sizer.Add(self.mpanel, 2, wx.ALL | wx.EXPAND , 5)
        
        sizer.Add(self.button_sizer, 0, wx.ALL | wx.ALIGN_RIGHT, 5)
        self.SetSizer(sizer)
        sizer.Fit(self)
        self.Layout()
 
    def OnOk(self, event):  
        msg = self.sanityChecks()
        if msg != '':
            wx.MessageBox(msg)
            return
        else: self.EndModal(wx.ID_OK)
        
    def sanityChecks(self):
        '''Override this method with specific tests for your application.'''
        return ''

    def OnCancel(self, event):  
        self.EndModal(wx.ID_CANCEL)

class WinDialog(MultiValueDialog):

    def makePanel(self):
        '''Override makePanel''' 
     
        self.fields = []
        self.titles = ("Horizontal", "Vertical")
        
        self.fields.append( (('xmin', 'Minimum Internuclear Distance'), 
                     ('xmax', 'Maximum Internuclear Distance'),
                     ('xtics', 'Radial Axis Ticks Spacing')) )
        self.fields.append((('absmin', 'Absolute Minimum Energy'),
                     ('absmax', 'Absolute Maximum Energy'),
                     ('winrange', 'Visible Energy Range'), ('ytics', 'Energy Axis Ticks Spacing')) )
        self.types = {'xmin': float, 'xmax': float, 'xtics': float,
                     'absmax': float, 'absmin': float, 'winrange': float,
                     'ytics': float }
        
        

    def sanityChecks(self):
        '''Defines input checks and error messages'''

        tests = (("xmin > xmax", 'Minimum Radius must be less than maximum Radius'),
                ("xtics > (xmax - xmin)",'Radial axis ticks spacing is too large'))
        msg = self.mpanel.panels[0].checkInput(self.types, tests=tests)
        if msg: return msg
        
        tests = (("absmin > absmax", 'Minimum energy must be less than maximum energy'),
                ("winrange > (absmax - absmin)",'Visible energy range is too large'),
                ("ytics > (absmax - absmin)",'Energy tick spacing is too large'))
        msg = self.mpanel.panels[1].checkInput(self.types, tests=tests)

        return msg


class WinDialog2(MultiValueDialog):
    def __init__(self, parent, props, flag=wx.HORIZONTAL, *args, **kwds):
        '''Extends MultiValueDialog to create a window settings panel with fields for
        xmin, xmax, xtics, ymin, ymax, and ytics.  '''
        MultiValueDialog.__init__(self, parent, fields, titles, props, flag=flag, *args, **kwds)
        self.SetTitle

    def makePanel(self):
        self.fields = []
        self.titles = ("Horizontal", "Vertical")
        self.fields.append( (('xmin', 'Minimum Internuclear Distance'), 
                     ('xmax', 'Maximum Internuclear Distance'),
                     ('xtics', 'Radial Axis Ticks Spacing')) )
        self.fields.append( (('ymin', 'Minimum Energy'), 
                     ('ymax', 'Maximum Energy'),
                     ('ytics', 'Radial Axis Ticks Spacing')) )
        self.types = {'xmin': float, 'xmax': float, 'xtics': float,
                     'ymax': float, 'absmin': float, 'winrange': float,
                     'ytics': float }
        self.mpanel = MultiValuePanel(self, self.fields, self.titles, self.props, flag=self.flag)
        

    def sanityChecks(self):
        '''Defines input checks and error messages'''

        tests = (("xmin > xmax", 'Minimum Radius must be less than maximum Radius'),
                ("xtics > (xmax - xmin)",'Radial axis ticks spacing is too large'))
        msg = self.mpanel.panels[0].checkInput(self.types, tests=tests)
        if msg: return msg
        
        tests = (("absmin > absmax", 'Minimum energy must be less than maximum energy'),
                ("winrange > (absmax - absmin)",'Visible energy range is too large'),
                ("ytics > (absmax - absmin)",'Energy tick spacing is too large'))
        msg = self.mpanel.panels[1].checkInput(self.types, tests=tests)

        return msg

        

if __name__ == '__main__':

    app = wx.PySimpleApp()  # Start the application
    size = []
    fields = []
    titles = ("Horizontal", "Vertical", "Window")
    fields.append( (('xmin', 'Minimum Internuclear Distance'), 
                     ('xmax', 'Maximum Internuclear Distance'),
                     ('xtics', 'Radial Axis Ticks Spacing')) )
    fields.append( (('ymin', 'Minimum Energy'), 
                     ('ymax', 'Maximum Energy'),
                     ('ytics', 'Radial Axis Ticks Spacing')) )
    fields.append((('absmin', 'Absolute Minimum Energy'),
                     ('absmax', 'Absolute Maximum Energy'),
                     ('winrange', 'Visible Energy Range')) )
    
    props = {'xmin' : 0., 'xmax' : 20.,'absmin': -5000.,'absmax': 30000.,
             'ymin': 10000, 'ymax': 20000, 'winrange': 10000, 'xtics':2,'ytics': 2000 }
    
    wx.InitAllImageHandlers()
    dialog = WinDialog(None, props=props)
    app.SetTopWindow(dialog)
    if dialog.ShowModal() == wx.ID_OK:
        dialog.Close(True)
        print dialog.mpanel.getOutput(dialog.types)
    else:
        dialog.Close(True)
    app.MainLoop()




